package com.wnxy.pms.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.common.jwt.JwtTemplate;
import com.wnxy.pms.common.api.Result;
import com.wnxy.pms.common.api.enums.impl.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;

/**
 * gateway网关过滤器：
 * 1、网关有两种类型的过滤器：
 * 1.1 GatewayFilterFactory 网关过滤器，是局部过滤器，只对配置了该过滤器的路由生效；
 * 1.2 GlobalFilter 全局过滤器，对所有的路由都生效，需要实现GlobalFilter接口
 * 通常，我们需要在网关过滤器进行鉴权，实现GlobalFilter该接口。拦截所有的请求。
 * <p>
 * 2、网关过滤器的执行顺序？
 * 2.1 GlobalFilter过滤器
 * A. 执行顺序由Ordered接口返回的值决定，值越小执行优先级越高
 * B. 如果多个全局过滤器的Ordered值一样，会根据过滤器字母顺序，排在前面的先执行；
 * 与spring包扫描由关系，因为包扫描根据文件顺序扫描的，扫描后封装到list中，
 * 所以字母顺序靠前的先被扫描，先执行；
 * 2.2 GlobalFilter全局过滤器与网关过滤器Order值一样，GlobalFilter执行优先级更高
 * 原因是因为：全局过滤器与网关过滤器最终会合并为一个过滤器集合，形成执行过滤链；
 * 是通过combined.addAll(gatewayFilters) addAll方法把网关过滤器添加到
 * 全局过滤器集合的末尾； 所以在过滤器大的集合中，全局过滤器排在前面就先执行。
 * 参见：FilteringWebHandler.handler方法
 */
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {

    @Autowired
    private JwtTemplate jwtTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 需求：以 /auth开头的路径需要进行认证
     * 步骤分析：
     * 1、获取请求路径，如果请求路径中包含auth，需要进行鉴权
     * 2、鉴权要求：必须要在请求头中携带token，格式：Bearer token
     *      "Authorization" "Bearer token"
     * 3、获取请求头，去掉Bearer空格，得到token
     * 4、通过JwtTemplate，校验token
     * 5、从Redis中获取token
     * 6、校验成功后，获取token中的userId
     * 7、重写请求头，把userId放入请求头中，再传递给各个微服务
     * 8、token续期
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //1、获取请求路径，如果请求路径中包含auth，需要进行鉴权
        //举例：/portal/promotion-seckill/authUser/order/1
        String path = request.getURI().getPath();
        AntPathMatcher matcher = new AntPathMatcher();
        // 参数1：路径匹配规则
        // 参数2：要匹配的路径
        boolean result = matcher.match("/**/auth/**", path);
        if (result) { // 说明当前访问路径需要进行鉴权

            //2、鉴权要求：必须要在请求头中携带token，格式：Bearer token
            String token = request.getHeaders().getFirst("Authorization");
            if (StringUtils.isEmpty(token)) {
                log.info("请求头中未携带token，请先登陆认证后再访问！");
                return error(response);
            }

            //3、获取请求头，去掉Bearer空格，得到token
            token = token.replace("Bearer ","");

            //4、通过JwtTemplate，校验token
            boolean verifyToken = false;
            try {
                // token解析失败异常：The token was expected 3 parts, but got 1.
                verifyToken = jwtTemplate.verifyToken(token);
            } catch (Exception e) {
                log.info("token不合法！");
                e.printStackTrace();
                return error(response);
            }
            if (!verifyToken) {
                log.info("token不合法！");
                return error(response);
            }

            //5、校验成功后，获取token中的userId
            String userId = (String) jwtTemplate.parseToken(token, "userId");

            //6、从Redis中获取token
            String redisToken =
                    (String) redisTemplate.opsForValue().get("user:token:" + userId);
            if (StringUtils.isEmpty(redisToken)) {
                log.info("Token已经过期！");
                return error(response);
            }

            //7、重写请求头，把userId放入请求头中，再传递给各个微服务
            ServerHttpRequest.Builder mutate = request.mutate();
            mutate.header("userId",userId);
            //8、token续期
            redisTemplate.expire("user:token:" + userId, Duration.ofMinutes(30));
        }

        // 如果请求路径中没有auth，直接放行，不进行鉴权！
        return chain.filter(exchange);
    }

    // 返回错误
    private Mono<Void> error(ServerHttpResponse response) {
        try {
            Result result = Result.fail(ResultCode.PERMISSION_NO_ACCESS);
            ObjectMapper objectMapper = new ObjectMapper();
            byte[] bytes = objectMapper.writeValueAsBytes(result);
            DataBuffer dataBuffer=response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(dataBuffer));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return response.writeWith(Mono.error(e));
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
